﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;

using Eco.ObjectRepresentation;

using CommandGraph.Model;
using CommonClasses.Model;
using CommonClasses.Services;
using CommonClasses.Logger;
using CommonClasses.Utility;
using Descriptions_DomainModel.Model;

namespace CommandGraph.Controls
{
    public class CommandGraphsFactory : IDocumentsFactory
    {
        #region Private fields

        private IEcoServiceProvider serviceProvider = null;
        /// <summary>
        /// Логгер, в который будут посылаться сообщения
        /// </summary>
        private ILogger logger = null;
        /// <summary>
        /// Список документов (редакторов)
        /// </summary>
        private List<CommandsGraphEditorPlugin> editors = new List<CommandsGraphEditorPlugin>();

        #endregion

        public CommandGraphsFactory(IEcoServiceProvider serviceProvider, LoggerManager manager)//ILogger logger)
        {
            if (serviceProvider == null)
                throw new NullReferenceException("ServiceProvider cannot be null!");

            this.serviceProvider = serviceProvider;
            logger = manager.GetLogger("CommandGraphsFactoryLogger");
        }

        #region IDocumentsFactory Members

        /// <summary>
        /// Удалить документ с диска и его редактор
        /// </summary>
        /// <param name="DocumentEditor"> (IDocumentEditor)редактор удаляемого документа </param>
        public void DeleteDocument(IDocumentEditor DocumentEditor)
        {
            RemoveOrDeleteDocument(DocumentEditor, true);
        }

        /// <summary>
        /// Дать список модулей компиляции
        /// </summary>
        /// <returns> 
        /// список ошибок
        /// </returns>
        /// <param name="compilationUnits"> список модулей компиляции </param>
        public ErrorReport GetCompilationUnits(out List<ICompilationUnit> compilationUnits)
        {
            compilationUnits = new List<ICompilationUnit>();
            var report = new ErrorReport(serviceProvider);
            foreach (var editor in editors.Where(editr => editr.IsCompiled))
            {
                CommandsGraphCompilationUnit curUnit = null;
                report.AddRange(editor.Compile(out curUnit));
                compilationUnits.Add(curUnit);
            }
            return report;
        }

        /// <summary>
        /// Проинициализировать указанием описания оборудования
        /// </summary>
        /// <param name="DomainModel"> (DomainModel) </param>
        public void Initialize(DomainModel DomainModel)
        {
            serviceProvider.GetEcoService<IEditorWithIntelliSense>().Initialize(DomainModel);
        }

        /// <summary>
        /// Загрузить из файла документ и создать ему редактор
        /// </summary>
        /// <returns> 
        /// редактор документа
        /// </returns>
        /// <param name="FileName"> (string) путь до файла</param>
        public IDocumentEditor LoadDocument(string FileName)
        {
            try
            {
                var newEditor = new CommandsGraphEditorPlugin();
                newEditor.Initialize(serviceProvider);

                var fakeGraph = new CommandsGraph(serviceProvider);
                newEditor.Graph = fakeGraph;

                var loader = newEditor.ToolBox.GetToolByName("CommandsGraphLoaderTool") as CommandsGraphLoaderTool;
                newEditor.Graph = loader.Load(FileName);

                while (fakeGraph.Entities.Count > 0)
                    fakeGraph.Entities[0].AsIObject().Delete();
                fakeGraph.AsIObject().Delete();

                newEditor.Path = Path.GetDirectoryName(FileName) + Path.DirectorySeparatorChar
                    + Path.GetFileNameWithoutExtension(FileName);
                editors.Add(newEditor);

                //foreach (var entity in newEditor.Graph.Entities)
                //    entity.Invalidate();

                return newEditor;
            }
            catch (Exception ex)
            {
                throw new LoadingDocumentException("Ошибка загрузки файла типа \"Граф комманд\":\n" +
                    FileName + "\n\nТекст ошибки:\n" + ex.Message);
            }
        }

        /// <summary>
        /// Создать новый документ и создать ему редактор
        /// </summary>
        /// <returns> 
        /// (IDocumentEditor)редактор к документу
        /// </returns>
        /// <param name="UsedModulesNames"> 
        /// (IList<string>) Имена, уже задействованные в проекте для других документов
        /// </param>
        public IDocumentEditor NewDocument(IList<string> UsedModulesNames)
        {
            var defaultPrefix = "ГрафКомманд_";
            var buf = 0;
            var usedNumbers = from editor in editors
                              let fileName = Path.GetFileName(editor.Path)
                              where fileName.Contains('_')
                              let number = fileName.Split('_')[1]
                              where int.TryParse(number, out buf)
                              orderby number ascending
                              select int.Parse(number);

            var searchResult = usedNumbers.Aggregate(new { Index = 0, Number = 1, Found = false },
                (acc, number) =>
                {
                    if (!acc.Found)
                        return (number != acc.Index + 1) ?
                            new { Index = acc.Index + 1, Number = number, Found = true }
                            : new { Index = acc.Index + 1, Number = acc.Number, Found = false };
                    else
                        return acc;
                });
            var newNumber = (searchResult.Found) ?
                searchResult.Number
                : searchResult.Index + 1;

            var newEditor = new CommandsGraphEditorPlugin();
            newEditor.Initialize(serviceProvider);

            newEditor.Graph = new CommandsGraph(serviceProvider);

            newEditor.Path = defaultPrefix + newNumber;

            editors.Add(newEditor);

            return newEditor;
        }

        /// <summary>
        /// Вызывается при выгрузке проекта
        /// </summary>
        public void OnProjectUnloaded()
        {
            var graphs = from editor in editors
                         where editor.Graph != null
                         select editor.Graph;

            editors.ForEach(editor => editor.Graph = null);

            //на этот момент уже всё удалено, но всё же:
            var count = graphs.Count();
            for (int i = 0; i < count; i++)
                if (!graphs.ElementAt(0).AsIObject().Deleted)
                {
                    var graph = graphs.ElementAt(0);
                    while (graph.Entities.Count > 0)
                        graph.Entities[0].AsIObject().Delete();
                    graph.AsIObject().Delete();
                }

            editors.Clear();

            GC.Collect();
        }

        /// <summary>
        /// Обновить список управляемых узлов
        /// </summary>
        /// <param name="compiledNodes"> 
        /// список управляемых узлов
        /// </param>
        public void RefreshCompiledNodes(IList<Node> compiledNodes)
        {
            throw new NotImplementedException("Oleg sayd this method is unused");
        }

        /// <summary>
        /// Исключить документ и его редактор, не удаляя файла
        /// </summary>
        /// <param name="DocumentEditor"> (IDocumentEditor)редактор исключаемого документа </param>
        public void RemoveDocument(IDocumentEditor DocumentEditor)
        {
            RemoveOrDeleteDocument(DocumentEditor, false);
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Удалить редактор, удалить (не всегда) файл с диска
        /// </summary>
        /// <param name="DocumentEditor">
        /// Удаляемый редактор
        /// </param>
        /// <param name="needDelete">
        /// true - удалить файл с диска, иначе - false
        /// </param>
        private void RemoveOrDeleteDocument(IDocumentEditor DocumentEditor, bool needDelete)
        {
            if (!(DocumentEditor is CommandsGraphEditorPlugin))
                throw new ArgumentTypeException(DocumentEditor.GetType(), typeof(CommandsGraphEditorPlugin));

            var graphEditor = DocumentEditor as CommandsGraphEditorPlugin;

            if (!(editors.Contains(graphEditor)))
                throw new ArgumentException("This DocumentEditor doesn't belongs to this fabric!");

            if (needDelete)
                File.Delete(graphEditor.Path);

            var graph = graphEditor.Graph;
            graphEditor.Graph = null;

            while (graph.Entities.Count > 0)
                graph.Entities[0].AsIObject().Delete();
            graph.AsIObject().Delete();

            editors.Remove(graphEditor);
        }

        #endregion
    }
}
